﻿namespace NovellSecurity
{
  using NovellSecurity.Caching;
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.DirectoryServices.Protocols;
  using System.Net;
  using System.Security.Cryptography.X509Certificates;
  using System.Text;
  using Sitecore.Diagnostics;
  using Sitecore.Configuration;

  /// <summary>
  /// The role provider.
  /// </summary>
  public class RoleProvider : System.Web.Security.RoleProvider
  {
    protected string BaseDn { get; set; }
    protected string Filter { get; set; }
    protected string UsersFilter { get; set; }
    protected string BaseDnUsers { get; set; }
    protected string eDireAttrGroupName { get; set; }
    protected string eDireAttrMember { get; set; }
    protected string eDireAttrUserName { get; set; }
    protected bool IsInitialized { get; set; }
    protected LdapConnection Connection { get; set; }

    public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
    {
      base.Initialize(name, config);
      this.IsInitialized = false;
      this.Filter = config["filter"];
      this.BaseDn = config["baseDn"];
      this.eDireAttrGroupName = config["groupNameAttribute"];
      this.eDireAttrMember = config["memberAttribute"];
      this.UsersFilter = config["usersFilter"];
      this.BaseDnUsers = config["baseDnUsers"];
      this.eDireAttrUserName = config["userNameAttribute"];
      this.Connection = ConnectionHelper.Connect(config["connectionStringName"], config["connectionUserDn"], config["connectionPassword"]);
      this.IsInitialized = false;
      try
      {
        this.Connection.Bind();
        this.IsInitialized = true;
      }
      catch (Exception e)
      {
        Log.Error("Novell eDirectory: troubles with connection. Details: " + e.Message, this);
      }
    }

    /// <summary>
    /// The add users to roles.
    /// </summary>
    /// <param name="usernames">
    /// The usernames.
    /// </param>
    /// <param name="roleNames">
    /// The role names.
    /// </param>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override void AddUsersToRoles(string[] usernames, string[] roleNames)
    {
      Log.Debug("Novell Security: Method invoked:  AddUsersToRoles(string[] usernames, string[] roleNames) is not implemented", this);
    }

    /// <summary>
    /// Gets or sets the application name.
    /// </summary>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override string ApplicationName
    {
      get
      {
        return this.ApplicationName;
      }
      set
      {
        this.ApplicationName = ApplicationName;
      }
    }

    /// <summary>
    /// The create role.
    /// </summary>
    /// <param name="roleName">
    /// The role name.
    /// </param>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override void CreateRole(string roleName)
    {
      Log.Debug("Novell Security: Method invoked:  CreateRole is not implemented", this);
    }

    /// <summary>
    /// The delete role.
    /// </summary>
    /// <param name="roleName">
    /// The role name.
    /// </param>
    /// <param name="throwOnPopulatedRole">
    /// The throw on populated role.
    /// </param>
    /// <returns>
    /// The <see cref="bool"/>.
    /// </returns>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
    {
      Log.Debug("Novell Security: Method invoked:  DeleteRole is not implemented", this);
      return false;
    }

    /// <summary>
    /// The find users in role.
    /// </summary>
    /// <param name="roleName">
    /// The role name.
    /// </param>
    /// <param name="usernameToMatch">
    /// The username to match.
    /// </param>
    /// <returns>
    /// The <see cref="string[]"/>.
    /// </returns>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override string[] FindUsersInRole(string roleName, string usernameToMatch)
    {
      Log.Debug("Novell Security: Method invoked: FindUsersInRole(string roleName, string usernameToMatch)", this);
      List<string> users = new List<string>();
      if (NovellCacheManager.IsCacheEmptyOrNull("NovellRolesCache"))
      {
        this.GetRolesFromEdirAndFillCache();
      }
      List<string> foundUsers = new List<string>();
      if (NovellCacheManager.GetRole(roleName)!=null)
      {
        List<string> allUsers = NovellCacheManager.GetRole(roleName).Members;
        string usernameToFind = usernameToMatch.ToLower().Replace("*", "");
        foreach (string currentUser in allUsers)
        {
          if (currentUser.ToLower().Contains(usernameToFind))
          {
            foundUsers.Add(currentUser);
          }
        }
      }
      return foundUsers.ToArray();
    }

    /// <summary>
    /// The get all roles.
    /// </summary>
    /// <returns>
    /// The <see cref="string[]"/>.
    /// </returns>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override string[] GetAllRoles()
    {
      Log.Debug("Novell Security: Method invoked: GetAllRoles()", this);
      List<NovellRole> allNovellRoles = new List<NovellRole>();
      List<string> roleNames = new List<string>();
      if (NovellCacheManager.IsCacheEmptyOrNull("NovellRolesCache"))
      {
        this.GetRolesFromEdirAndFillCache();
      }
      foreach (NovellRole novellRole in NovellCacheManager.GetAllRoles())
      {
        roleNames.Add(novellRole.Name);
      }
      return roleNames.ToArray();
    }

    /// <summary>
    /// The get roles for user.
    /// </summary>
    /// <param name="username">
    /// The username.
    /// </param>
    /// <returns>
    /// The <see cref="string[]"/>.
    /// </returns>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override string[] GetRolesForUser(string username)
    {
      Log.Debug("Novell Security: Method invoked: GetRolesForUser(string username)", this);
      List<string> roles = new List<string>();
      if (NovellCacheManager.IsCacheEmptyOrNull("NovellRolesCache"))
      {
        this.GetRolesFromEdirAndFillCache();
      }
      foreach (NovellRole role in NovellCacheManager.GetAllRoles())
      {
        if (role.Members.Contains(username)) 
        {
          roles.Add(role.Name);
        }
      }
      return roles.ToArray();
    }

    /// <summary>
    /// The get users in role.
    /// </summary>
    /// <param name="roleName">
    /// The role name.
    /// </param>
    /// <returns>
    /// The <see cref="string[]"/>.
    /// </returns>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override string[] GetUsersInRole(string roleName)
    {
      Log.Debug("Novell Security: Method invoked: GetUsersInRole(string roleName)", this);
      List<string> users = new List<string>();
      if (NovellCacheManager.IsCacheEmptyOrNull("NovellRolesCache"))
      {
        this.GetRolesFromEdirAndFillCache();
      }
      NovellRole role=NovellCacheManager.GetRole(roleName);
      if(role!=null)
      {
        users = role.Members;
      }
      return users.ToArray();
    }

    /// <summary>
    /// The is user in role.
    /// </summary>
    /// <param name="username">
    /// The username.
    /// </param>
    /// <param name="roleName">
    /// The role name.
    /// </param>
    /// <returns>
    /// The <see cref="bool"/>.
    /// </returns>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override bool IsUserInRole(string username, string roleName)
    {
      Log.Debug("Novell Security: Method invoked: IsUserInRole(string username, string roleName)", this);
      if (NovellCacheManager.IsCacheEmptyOrNull("NovellRolesCache"))
      {
        this.GetRolesFromEdirAndFillCache();
      }
      NovellRole role=NovellCacheManager.GetRole(roleName);
      if (role != null)
      {
        if (role.Members.Contains(username))
        {
          return true;
        }
      }
      return false;
    }


    /// <summary>
    /// The remove users from roles.
    /// </summary>
    /// <param name="usernames">
    /// The usernames.
    /// </param>
    /// <param name="roleNames">
    /// The role names.
    /// </param>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
    {
      Log.Debug("Novell Security: Method invoked:  RemoveUsersFromRoles is not implemented", this);
    }

    /// <summary>
    /// The role exists.
    /// </summary>
    /// <param name="roleName">
    /// The role name.
    /// </param>
    /// <returns>
    /// The <see cref="bool"/>.
    /// </returns>
    /// <exception cref="NotImplementedException">
    /// </exception>
    public override bool RoleExists(string roleName)
    {
      Log.Debug("Novell Security: Method invoked: RoleExists(string roleName)", this);
      if (NovellCacheManager.IsCacheEmptyOrNull("NovellRolesCache"))
      {
        this.GetRolesFromEdirAndFillCache();
      }
      if (NovellCacheManager.GetRole(roleName)!=null)
      {
        return true;
      }
      else
      {
        return false;
      }
    }

    /// <summary>
    /// Determines whether [is role unique] [the specified roles].
    /// </summary>
    /// <param name="roles">The roles.</param>
    /// <param name="newRoleName">New name of the role.</param>
    /// <returns></returns>
    protected virtual bool IsRoleUnique(List<NovellRole> roles, string newRoleName)
    {
      foreach(NovellRole role in roles)
      {
        if (role.Name==newRoleName)
        {
          Log.Warn("Novell eDirectory: The group name " + newRoleName + " is not unique. Please try to use another property for mapping the groups (insted of " + this.eDireAttrGroupName + ") or change the group name on the eDir side.", this);
          return false;
        }
      }
      return true;
    }

    /// <summary>
    /// Gets the roles from edir and fill cache.
    /// </summary>
    public virtual void GetRolesFromEdirAndFillCache()
    {
      if (this.IsInitialized)
      {
        NovellCacheManager.Clear("NovellRolesCache");
        List<NovellRole> roles = new List<NovellRole>();
        try
        {
          SearchRequest request = new SearchRequest(this.BaseDn, "(&(" + this.Filter + "))", System.DirectoryServices.Protocols.SearchScope.Subtree, this.eDireAttrGroupName, this.eDireAttrMember);
          SearchResponse response;
          request.SizeLimit = int.Parse(Settings.GetSetting("Novell.Roles.MaxSizeLimit"));
          TimeSpan timeout = Settings.GetTimeSpanSetting("Novell.Roles.RequestTimeout", "00:02:00");
          try
          {
            response = (SearchResponse)this.Connection.SendRequest(request, timeout);
          }
          catch (DirectoryOperationException e)
          {
            response = (SearchResponse)e.Response;
          }
          if (response.Entries.Count > 0)
          {
            Log.Debug("Novell eDirectory: Roles found = " + response.Entries.Count, this);
            foreach (SearchResultEntry group in response.Entries)
            {
              if (this.IsRoleUnique(roles, group.Attributes[this.eDireAttrGroupName][0].ToString()))
              {
                string roleName = group.Attributes[this.eDireAttrGroupName][0].ToString();
                DirectoryAttribute membersDNs = group.Attributes[this.eDireAttrMember];
                List<string> membersNames = this.GetMembersNamesFromEdir(membersDNs);
                NovellRole uniqueRole = new NovellRole(roleName.ToLower(), membersNames);
                roles.Add(uniqueRole);
                NovellCacheManager.AddRole(uniqueRole);
              }
            }
          }
          else
          {
            Log.Debug("Novell eDirectory: No roles are found", this);
          }
        }
        catch (Exception exc)
        {
          Log.Error("Novell eDirectory: " + exc.Message + " || " + exc.StackTrace, this);
        }
      }
    }

    /// <summary>
    /// Gets the memebrs from edir.
    /// </summary>
    /// <param name="roleName">Name of the role.</param>
    /// <returns></returns>
    protected virtual List<string> GetMembersNamesFromEdir(DirectoryAttribute membersDNs)
    {
      List<string> membersNames = new List<string>();
      if (this.IsInitialized)
      {
        if (membersDNs != null)
        {
          if (membersDNs.Count > 0)
          {
            foreach (Byte[] memberDN in membersDNs)
            {
              SearchRequest membersRequest = new SearchRequest(Encoding.Default.GetString(memberDN), "(" + this.UsersFilter + ")", SearchScope.Subtree, this.eDireAttrUserName);
              SearchResponse membersResponse = (SearchResponse)this.Connection.SendRequest(membersRequest);
              if (membersResponse.Entries.Count > 0)
              {
                membersNames.Add(membersResponse.Entries[0].Attributes[this.eDireAttrUserName][0].ToString().ToLower());
              }
            }
          }
        }
      }
      return membersNames;
    }
  }
}
